package edu.hawaii.wattwaiter.wattdepotcommands;

import java.util.ArrayList;
import java.util.List;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.client.BadXmlException;
import org.wattdepot.client.MiscClientException;
import org.wattdepot.client.NotAuthorizedException;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.resource.source.jaxb.Source;
import org.wattdepot.util.tstamp.Tstamp;

/**
 * Contacts a WattDepot server and collects the data required to create
 * a chart with the specified arguments. <br />
 * <br />
 * Collecting a <code>List</code> of doubles from the default server that
 * represent hourly data for the time between November 25th, 2009 and
 * November 26th, 2009.  The data is gathered from the source SIM_OAHU_GRID.
 * <pre>
 *  ChartDataCollector data = new ChartDataCollector( null );
 *  List&lt;Double&gt; results = data.getChartData( "carbon", "2009-10-25", "2009-10-26", 
 *      "SIM_OAHU_GRID", "hour" );
 * </pre>
 * Collecting all of the sub-sources of a source SIM_OAHU_GRID.
 * If the source given is virtual, it will also be in the List.
 * <pre>
 *  ChartDataCollector data = new ChartDataCollector( null );
 *  List&lt;Double&gt; results = data.getSubSources( "SIM_OAHU_GRID" );
 * </pre>
 * @author David Mau
 */
public class ChartDataCollector extends WattDepotClientCommand {
  // A variable to hold this class' name
  private static String name = "ChartDataCollector: ";
  // A variable to help check how long the date String should be.
  private static String dateFormat = "YYYY-MM-DD";
  // A variable for how long the sampling interval of the data should be.
  private static int samplingInterval = 60;
  
  /**
   * Readies the client for connecting to a WattDepot server.
   * 
   * @see WattDepotClientCommand
   * @param anotherServer An optional argument that can be given to attempt to
   * connect to a different WattDepot server than the default.
   */
  public ChartDataCollector( String anotherServer ) {
    // Set up the client for the WattDepot server.
    super( anotherServer );
  }

  /**
   * Collects the requested data from the WattDepot server and gives the data
   * back untouched as a <code>List</code> of doubles.  For all of the data,
   * the sampling interval is 60 minutes.
   * 
   * @param carbonOrEnergy Must be either "carbon" or "energy".  Queries the WattDepot
   * server using the appropriate command depending on this choice.
   * @param startDay The day to start collecting information from.  Form of the date should be
   * in YYYY-MM-DD, where Y is for years, M is for months, and D is for days.
   * @param endDay The day to stop collecting information at.  The information will not include
   * this day.  Form of the date should be in YYYY-MM-DD, where Y is for years, M is for months,
   * and D is for days.
   * @param source The source to collect the information from the server about.
   * @param timeFrame Must be either "hour", "day", or "week".  Causes the returned
   * <code>List</code> to have data for every hour, day, or week in the time duration given.
   * @return A <code>List</code> of doubles that are the data points for the requested
   * information.  Will return null if an error has occurred.
   */
  public List<Double> getChartData( String carbonOrEnergy, String startDay, String endDay, 
      String source, String timeFrame ) {
    
    List<Double> output = new ArrayList<Double>();
    
    // Stop right away if we cannot contact the server.
    if (!client.isHealthy()) {
      // If in debug mode, print out that the connection was bad.
      if ( consoleDebugMode ) {
        System.out.println( name + serverConnectionError + 
            client.getWattDepotUri() );
      }
      return null;
    }
    
    // If any arguments are null, return null as an error.
    if ( carbonOrEnergy == null || startDay == null || endDay == null || source == null ||
        timeFrame == null ) {
      // If in debug mode, print out that arguments were missing.
      if ( consoleDebugMode ) {
        System.out.println( name + nullArguments );
      }
      return null;
    }
    
    // The carbonOrEnergy argument must be either carbon or energy.
    // If it isn't, return the all zero list as an error.
    if ( carbonOrEnergy.compareTo("carbon") != 0 && carbonOrEnergy.compareTo("energy") != 0 ) {
      // If in debug mode, print out the appropriate error.
      if ( consoleDebugMode ) {
        System.out.println( name + notCarbonOrEnergy + carbonOrEnergy );
      }
      return null;
    }
    
    // The timeFrame argument must be "hour", "day", or "week".
    // If it isn't, return the all zero list as an error.
    if ( timeFrame.compareTo( "hour" ) != 0 && timeFrame.compareTo( "day" ) != 0 && 
        timeFrame.compareTo( "week" ) != 0 ) {
      // If in debug mode, print out the appropriate error.
      if ( consoleDebugMode ) {
        System.out.println( name + notHourDayWeek + timeFrame );
      }
      return null;
    }
    
    // If either of the dates given is not the proper length,
    // Return the all zero list as an error.
    if ( startDay.length() != dateFormat.length() ) {
      // If in debug mode, print out that the given date was bad.
      if ( consoleDebugMode ) {
        System.out.println( name + badDate + startDay );
      }
      return null;
    }
    
    // If either of the dates given is not the proper length,
    // Return the all zero list as an error.
    if ( endDay.length() != dateFormat.length() ) {
      // If in debug mode, print out that the given date was bad.
      if ( consoleDebugMode ) {
        System.out.println( name + badDate + endDay );
      }
      return null;
    }
    
    // Convert the starting date into an XML one.
    XMLGregorianCalendar xmlStartTime = null;
    try {
      xmlStartTime = Tstamp.makeTimestamp( startDay );
    }
    catch (Exception e) {
      // If in debug mode, print out that the given date was bad.
      if ( consoleDebugMode ) {
        System.out.println( name + badDate + startDay );
      }
      return null;
    }
    
    // Convert the end date into an XML one.
    XMLGregorianCalendar xmlEndTime = null;
    try {
      xmlEndTime = Tstamp.makeTimestamp( endDay );
    }
    catch (Exception e) {
      // If in debug mode, print out that the given date was bad.
      if ( consoleDebugMode ) {
        System.out.println( name + badDate + endDay );
      }
      return null;
    }
    
    // Test to make sure that the start time is less than the end time.
    int temporalTest = xmlStartTime.compare(xmlEndTime);
    if ( temporalTest != DatatypeConstants.LESSER ) {
      // If in debug mode, print out that the given dates were bad.
      if ( consoleDebugMode ) {
        System.out.println( name + temporalError );
      }
      return null;
    }
    
    // Get all of the information.
    while ( xmlStartTime.compare(xmlEndTime) == DatatypeConstants.LESSER ) {
      double tempValue;
      XMLGregorianCalendar endOfInterval = xmlStartTime;
      
      // Set the appropriate end of the sampling for the data point.
      if ( timeFrame.compareTo( "hour" ) == 0 ) {
        endOfInterval = Tstamp.incrementHours( endOfInterval, 1 );
      }
      else if ( timeFrame.compareTo( "day" ) == 0 ) {
        endOfInterval = Tstamp.incrementDays( endOfInterval, 1 );
      }
      else {
        endOfInterval = Tstamp.incrementDays( endOfInterval, 7 );
      }
      
      // If carbon is what should be measured,
      if ( carbonOrEnergy.compareTo( "carbon" ) == 0 ) {
        // Collect data for the time interval from the server
        try {
          tempValue = client.getCarbonEmitted( source, xmlStartTime, 
              endOfInterval, samplingInterval );
        }
        catch (NotAuthorizedException exception) {
          // If in debug mode, print out that the client was not authorized
          // to view this information.
          if ( consoleDebugMode ) {
            System.out.println( name + notAuthorized + exception );
          }
          return null;
        }
        catch (ResourceNotFoundException exception) {
          // If in debug mode, print out that the resource was not found.
          if ( consoleDebugMode ) {
            System.out.println( name + notAuthorized + exception );
          }
          return null;
        }
        catch (BadXmlException exception) {
          // If in debug mode, print out that the server returned bad xml.
          if ( consoleDebugMode ) {
            System.out.println( name + badXml + exception );
          }
          return null;
        }
        catch (MiscClientException exception) {
          // If in debug mode, print out that the client encountered an unknown error.
          if ( consoleDebugMode ) {
            System.out.println( name + miscClient + exception );
          }
          return null;
        }
      }
      // Otherwise, energy is what should be measured.
      else {
        // Collect data for the time interval from the server
        try {
          tempValue = client.getEnergyGenerated( source, xmlStartTime, 
              endOfInterval, samplingInterval );
        }
        catch (NotAuthorizedException exception) {
          // If in debug mode, print out that the client was not authorized
          // to view this information.
          if ( consoleDebugMode ) {
            System.out.println( name + notAuthorized + exception );
          }
          return null;
        }
        catch (ResourceNotFoundException exception) {
          // If in debug mode, print out that the resource was not found.
          if ( consoleDebugMode ) {
            System.out.println( name + notAuthorized + exception );
          }
          return null;
        }
        catch (BadXmlException exception) {
          // If in debug mode, print out that the server returned bad xml.
          if ( consoleDebugMode ) {
            System.out.println( name + badXml + exception );
          }
          return null;
        }
        catch (MiscClientException exception) {
          // If in debug mode, print out that the client encountered an unknown error.
          if ( consoleDebugMode ) {
            System.out.println( name + miscClient + exception );
          }
          return null;
        }
      }
      
      // After getting the value from the server, store the value in the output List
      output.add( tempValue );
      
      // Increment the starting time appropriately.
      xmlStartTime = endOfInterval;
    }
    
    return output;
  }
  
  /**
   * Collects the non-virtual sub-sources of the given source.
   * 
   * @param source The source to get all of the sub-sources for.
   * @return A <code>List</code> of the non-virtual sub-sources for the given source.
   * The source given to this method will also be the last item in the list.
   */
  public List<String> getAllSubSources ( String source ) {
    // Stop right away if we cannot contact the server.
    if (!client.isHealthy()) {
      // If in debug mode, print out that the connection was bad.
      if ( consoleDebugMode ) {
        System.out.println( name + serverConnectionError + 
            client.getWattDepotUri() );
      }
      return null;
    }
    
    // If the source given is null, return null as an error state.
    if ( source == null ) {
      // If in debug mode, print out that the connection was bad.
      if ( consoleDebugMode ) {
        System.out.println( name + nullSource );
      }
      return null;
    }
    
    List<String> output = new ArrayList<String>();
    boolean virtualSource;
    
    // Create a list for all of the sources that the program will need to go through.
    List<Source> allSubSources = new ArrayList<Source>();
    
    // Place all non-virtual sources that the parent source links to into the list.
    try {
      // Get the source information from the client using the argument passed.
      Source parent = client.getSource( source );
      
      // If the parent is a virtual source, remember that it is.
      virtualSource = parent.isVirtual();
      
      // Add the source to the list of fuel sources the program needs to go through.
      allSubSources.add( parent );
      
      int counter = 0;
        
      // Go through the list of uncheckedSources
      while ( counter < allSubSources.size() ) {
        
        // If the source is a virtual source,
        if ( allSubSources.get(counter).isVirtual() ) {
          
          // Get all sub-sources from the parent.
          List<String> children = allSubSources.get(counter).getSubSources().getHref();
          // Add them all as sources to the list.
          for ( String child : children ) {
            
            // Break the url up.
            String[] urlBreak = child.split("/");
            ArrayList<String> brokenUrl = new ArrayList<String>();
            for ( String url : urlBreak ) {
              brokenUrl.add( url );
            }
            // Figure out the last part of the url.
            int lastIndex = brokenUrl.size() - 1;
            
            // Get the source from the client.
            Source newAddition = client.getSource( brokenUrl.get(lastIndex) );
            // Add the child to the list.
            allSubSources.add(newAddition);
          }
          
          // Remove the virtual source from the list
          allSubSources.remove(counter);
        }
        // Otherwise, the source is non-virtual.
        else {
          // Move onto the next item on the list.
          counter++;
        }
      }
    }
    catch (NotAuthorizedException exception) {
      // If in debug mode, print out that the client was not authorized to view this information.
      if ( consoleDebugMode ) {
        System.out.println( name + notAuthorized + exception );
      }
      // Return null as an error state.
      return null;
    }
    catch (ResourceNotFoundException exception) {
      // If in debug mode, print out that the resource was not found.
      if ( consoleDebugMode ) {
        System.out.println( name + notAuthorized + exception );
      }
      // Return null as an error state.
      return null;
    }
    catch (BadXmlException exception) {
      // If in debug mode, print out that the server returned bad xml.
      if ( consoleDebugMode ) {
        System.out.println( name + badXml + exception );
      }
      // Return null as an error state.
      return null;
    }
    catch (MiscClientException exception) {
      // If in debug mode, print out that the client encountered an unknown error.
      if ( consoleDebugMode ) {
        System.out.println( name + miscClient + exception );
      }
      // Return null as an error state.
      return null;
    }

    // Add the names of the sub sources to the output list.
    for ( Source singleSource : allSubSources ) {
      output.add( singleSource.getName() );
    }
    
    // Add the initial source that was given in the arguments,
    // if that source was virtual.
    if ( virtualSource ) {
      output.add( source );
    }
    
    return output;
  }
}
